<?php

namespace EthanZ\HyperfExt\Utils;

use EthanZ\HyperfExt\Redis\CommonRedis;
use Countable;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Snowflake\IdGeneratorInterface;
use Hyperf\Utils\Str;

/**
 * 其他工具类
 */
class Tools
{


    /**
     * 根据sfId生成新同时间段sfId
     *
     * @param int $sfId
     *
     * @return int
     */
    public static function getSfIdByOther(int $sfId): int
    {
        $idGeneratorInterface = make(IdGeneratorInterface::class);
        $time                 = $idGeneratorInterface->degenerate($sfId)->getTimestamp() / 1000;
        $snackIdGen           = new MyMetaGenerator((int)$time);

        return make(IdGeneratorInterface::class)->generate($snackIdGen->generate());
    }


    /**
     * 获取头部信息
     *
     * @param string $key
     *
     * @return mixed
     */
    public static function gertHeader(string $key): mixed
    {
        return make(RequestInterface::class)->header($key);
    }


    /**
     * 字符转换
     *
     * @param mixed  $var                数值
     * @param int    $decimals           小数位
     * @param string $thousandsSeparator 千分位
     *
     * @return string|null
     */
    public static function toNumber(mixed $var, int $decimals = 4, string $thousandsSeparator = ''): ?string
    {
        if (!is_numeric($var)) {
            return null;
        }
        if (is_string($var)) {
            // 千分位替换
            $var = str_replace(',', '', $var);
        }

        return number_format((float)$var, $decimals, '.', $thousandsSeparator);
    }


    /**
     * 系统自增数字（主要用于唯一字符）
     *
     * @param string $type    类型
     * @param int    $timeout 超时时间
     *
     * @return string
     */
    public static function incrNo(string $type = 'nml', int $timeout = 0): string
    {
        return make(CommonRedis::class)->incr('INCR_NO', $timeout, $type);
    }


    /**
     * 获取订单号
     *
     * @param string $prefix 前缀
     * @param string $type   类型
     * @param int    $length 尾数最大长度
     *
     * @return string
     */
    public static function orderNo(string $prefix = '', string $type = 'ord', int $length = 6): string
    {
        // 生成订单号.
        $incrNo = self::decimalToBase36(self::incrNo($type));
        if (strlen($incrNo) < $length) {
            $incrNo = str_repeat('0', $length - strlen($incrNo)) . $incrNo;
        }

        $orderNo = date('ymdHis') . $incrNo;

        return $prefix . $orderNo;
    }


    /**
     * 数组转树形结构
     *
     * @param array      $list
     * @param int|string $pid
     * @param string     $pidField
     * @param string     $pkField
     * @param string     $childrenName
     * @param int        $level
     *
     * @return array
     */
    public static function arrToTree(
        array      &$list,
        int|string $pid = 0,
        string     $pidField = 'pid',
        string     $pkField = 'id',
        string     $childrenName = 'children',
        int        $level = 1
    ): array
    {
        $data = [];
        foreach ($list as $k => $val) {
            if (!isset($val[$pidField], $val[$pkField])) {
                continue;
            }
            if ($val[$pidField] === $pid) {
                $temp   = $val + [
                        'level' => $level,
                        $childrenName           => self::arrToTree(
                            $list,
                            $val[$pkField],
                            $pidField,
                            $pkField,
                            $childrenName,
                            $level + 1
                        ),
                    ];
                $data[] = $temp;
                unset($list[$k]);
            }
        }

        return $data;
    }


    /**
     * 树形结构转数组
     *
     * @param array  $tree
     * @param string $childrenName
     * @param int    $level
     * @param array  $arr
     *
     * @return array
     */
    public static function treeToArr(
        array  $tree,
        string $childrenName = 'children',
        int    $level = 1,
        array  &$arr = []
    ): array
    {
        foreach ($tree as $val) {
            $children = $val[$childrenName] ?? [];
            unset($val[$childrenName]);
            $arr[] = array_merge($val, ['level' => $level]);
            if ($children) {
                self::treeToArr($children, $childrenName, $level + 1, $arr);
            }
        }

        return $arr;
    }


    /**
     * 获取ip
     *
     * @param int $type 1普通格式 2int格式
     *
     * @return mixed|string
     */
    public static function getIp(int $type = 1): int|string
    {
        $server = make(RequestInterface::class)->getServerParams();
        if (isset($server['http_client_ip'])) {
            $ip = $server['http_client_ip'];
        } elseif (isset($server['http_x_real_ip'])) {
            $ip = $server['http_x_real_ip'];
        } elseif (isset($server['http_x_forwarded_for'])) {
            //部分CDN会获取多层代理IP，所以转成数组取第一个值
            $arr = explode(',', $server['http_x_forwarded_for']);
            $ip = $arr[0];
        } else {
            $ip = $server['remote_addr'];
        }

        return $type == 1 ? $ip : self::ipToInt($ip);
    }


    /**
     * IP转int
     *
     * @param string $ip
     *
     * @return int
     */
    public static function ipToInt(string $ip): int
    {
        return sprintf('%u', ip2long($ip));
    }


    /**
     * 根据多个雪花ID获取所有分表年份
     *
     * @param array $snackIds 雪花id
     * @param int   $minYear  最小年份
     *
     * @return array
     */
    public static function getYearsBySfIds(array $snackIds, int $minYear = 0): array
    {
        $idGeneratorInterface = make(IdGeneratorInterface::class);
        $years                = [];
        try {
            foreach ($snackIds as $v) {
                $time    = (int)($idGeneratorInterface->degenerate((int)$v)->getTimestamp() / 1000);
                $year    = date('Y', $time);
                $year    = $minYear && $year < $minYear ? $minYear : $year;
                $years[] = $year;
            }
        } catch (\Throwable $e) {
            if ($minYear) {
                $years = [$minYear];
            } else {
                $years = [null];
            }
        }
        $years = array_unique($years);
        sort($years);

        return $years;
    }


    /**
     * 判断当前值是否为空.
     *
     * @param mixed $value
     *
     * @return bool
     */
    public static function blank(mixed $value): bool
    {
        $result = empty($value);
        if (is_null($value)) {
            $result = true;
        }

        if (is_string($value)) {
            $result = trim($value) === '';
        }

        if (is_numeric($value) || is_bool($value)) {
            $result = false;
        }

        if ($value instanceof Countable) {
            $result = count($value) === 0;
        }

        return $result;
    }


    /**
     * token 生成
     *
     * @return string
     */
    public static function token(): string
    {
        $salt = Str::random(8);
        $str  = md5(uniqid(md5(microtime(true)), true));

        return sha1($str . $salt);
    }


    /**
     * 开始时间结束时间整理
     *
     * @param int $startTime 开始时间.
     * @param int $endTime   结束时间.
     *
     * @return array
     */
    public static function betweenTime(int $startTime, int $endTime): array
    {
        $betweenTime = [];
        if ($startTime || $endTime) {
            $startTime = $startTime ?: 0;
            $endTime   = $endTime ?: 3999999999;
            // 大小判断.
            if ($startTime <= $endTime) {
                $betweenTime = [$startTime, $endTime];
            } else {
                $betweenTime = [$endTime, $startTime];
            }
        }

        return $betweenTime;
    }


    /**
     * 10进制转36进制
     *
     * @param int $decimal 十进制值
     *
     * @return string
     */
    public static function decimalToBase36(int $decimal): string
    {
        if ($decimal < 0) {
            return '';
        }

        $map = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I',
                'J','K','L', 'M','N','O','P', 'Q','R','S','T','U','V','W','X','Y','Z'];
        $base36 = '';
        do {
            $base36 = $map[($decimal % 36)] . $base36;
            $decimal /= 36;
        } while ($decimal >= 1);

        return $base36;
    }


    /**
     * 10进制转64进制
     *
     * @param int $decimal 十进制值
     *
     * @return string
     */
    public static function decimalToBase64(int $decimal): string
    {
        if ($decimal < 0) {
            return '';
        }

        $map = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I',
                'J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b',
                'c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u',
                'v','w','x','y','z','_','='];
        $base64 = '';
        do {
            $base64 = $map[($decimal % 64)] . $base64;
            $decimal /= 64;
        } while ($decimal >= 1);

        return $base64;
    }


    /**
     * 时间格式化
     *
     * @param int             $time   时间
     * @param string|callable $format 时间格式
     *
     * @return string
     */
    public static function timeToFormat(int $time, string|callable $format): string
    {
        return is_callable($format) ? $format($time) : date($format, $time);
    }


    /**
     * 获取两时间区间的所有时间
     *
     * @param int                  $startTime   开始时间
     * @param int                  $endTime     结束时间
     * @param string               $type        类型
     * @param string|callable|null $groupFormat 格式
     * @param int                  $step        跨度
     *
     * @return array
     */
    public static function dateGroup(
        int                  $startTime,
        int                  $endTime,
        string               $type,
        null|string|callable $groupFormat = null,
        int                  $step = 1
    )
    {
        if (is_null($groupFormat)) {
            $groupFormat = match ($type) {
                'hour'    => 'Y-m-d H',
                'day'     => 'Y-m-d',
                'week'    => 'Y年第W周',
                'month'   => 'Y-m',
                'quarter' => fn(int $time) => date('Y', $time) . '第' . ceil(date('n', $time) / 3) . '季度',
                'year'    => 'Y',
            };
        }
        $res = [];
        $i   = 0;
        switch ($type) {
            default:
            case 'day':
                $tempTime = strtotime(date('Y-m-d', $startTime));
                $endTime  = strtotime(date('Y-m-d', strtotime("- {$step} day +1 day", $endTime)));
                while ($tempTime < $endTime) {
                    $tempTime = strtotime('+' . $i . ' day', $startTime);
                    $res[]    = [
                        'name'  => self::timeToFormat($tempTime, $groupFormat),
                        'start' => strtotime(date('Y-m-d 00:00:00', $tempTime)),
                        'end'   => strtotime(date('Y-m-d 23:59:59', $tempTime)),
                    ];
                    $i        += $step;
                }
                break;
            case 'hour':
                $tempTime = strtotime(date('Y-m-d H' . ':00:00', $startTime));
                $endTime  = strtotime(date('Y-m-d H' . ':00:00', strtotime("- {$step} hour +1 hour", $endTime)));
                while ($tempTime < $endTime) {
                    $tempTime = strtotime('+' . $i . ' hour', $startTime);
                    $res[]    = [
                        'name'  => self::timeToFormat($tempTime, $groupFormat),
                        'start' => strtotime(date('Y-m-d H:00:00', $tempTime)),
                        'end'   => strtotime(date('Y-m-d H:59:59', $tempTime)),
                    ];
                    $i        += $step;
                }
                break;
            case 'week':
                $tempTime = strtotime('this week Monday', $startTime);
                $endTime  = strtotime('this week Sunday', strtotime("-{$step} week +1 week", $endTime));
                while ($tempTime < $endTime) {
                    $week     = strtotime('this week Monday +' . $i . ' week', $startTime);
                    $tempTime = strtotime('this week Sunday 23:59:59', $week);
                    $res[]    = [
                        'name'  => self::timeToFormat($week, $groupFormat),
                        'start' => strtotime('this week Monday', $week),
                        'end'   => $tempTime
                    ];
                    $i        += $step;
                }
                break;
            case 'month':
                $tempTime = strtotime(date('Y-m-01', $startTime));
                $endTime  = strtotime(date('Y-m-t', strtotime("- {$step} month +1 month", $endTime)));
                while ($tempTime < $endTime) {
                    $month    = strtotime('first day of +' . $i . ' month', $startTime);
                    $tempTime = strtotime(date('Y-m-t', $month));
                    $res[]    = [
                        'name'  => self::timeToFormat($month, $groupFormat),
                        'start' => strtotime(date('Y-m-01', $month)),
                        'end'   => $tempTime,
                    ];
                    $i        += $step;
                }
                break;
            case 'quarter':
                $tempTime    = strtotime(date('Y-m', $startTime));
                $quarterStep = $step * 3;
                $endTime     = date('Y-m', strtotime("- {$quarterStep} month +3 month", $endTime));
                while ($tempTime < $endTime) {
                    $quarter  = strtotime('first day of +' . $i . ' month', $startTime);
                    $q        = (int)ceil(date('n', $quarter) / 3);
                    $tempTime = strtotime(date('Y-m-t', mktime(23, 59, 59, $q * 3, 1, (int)date('Y', $quarter))));
                    $res[]    = [
                        'name'  => self::timeToFormat($quarter, $groupFormat),
                        'start' => strtotime(date('Y-m-01', mktime(0, 0, 0, $q * 3 - 2, 1, (int)date('Y', $quarter)))),
                        'end'   => $tempTime,
                    ];
                    $i        += 3 + $step;
                }
                break;
            case 'year':
                $tempTime = strtotime(date('Y-01-01', $startTime));
                $endTime  = strtotime(date('Y-12-31 23:59:59', strtotime("- {$step} year +1 year", $endTime)));
                while ($tempTime < $endTime) {
                    $year     = strtotime('+' . $i . ' year', $startTime);
                    $tempTime = strtotime(date('Y-12-31 23:59:59', $year));
                    $res[]    = [
                        'name'  => self::timeToFormat($year, $groupFormat),
                        'start' => strtotime(date('Y-01-01', $year)),
                        'end'   => strtotime(date('Y-12-31 23:59:59', $year)),
                    ];
                    $i        += $step;
                }
                break;
        }

        return $res;
    }
}
